using System;
using System.ComponentModel;
using System.Reflection;

namespace Debugging.Form2
{
    public class Presenter
    {
        # region Constructors
        public Presenter(IModel model, IView view)
        {
            _model = model;
            _view = view;
            RegisterDelegates();
        }
        #endregion

        #region Delegates
        private void RegisterDelegates()
        {
            View.PropertyChanged += new PropertyChangedEventHandler(View_PropertyChanged);
            Model.PropertyChanged += new PropertyChangedEventHandler(Model_PropertyChanged);
            Model.ShowView += new EventHandler(Model_ShowView);
            View.AcceptChanges += new EventHandler(View_AcceptChanges);
            Model.NotifyUser += new EventHandler<NotifyUserEventArgs>(Model_NotifyUser);
        }
        protected virtual void View_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            SetProperty(View, Model, e.PropertyName);
        }
        protected virtual void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            SetProperty(Model, View, e.PropertyName);
        }
        protected virtual void SetProperty(object source, object target, string propertyName)
        {
            object val = source.GetType().InvokeMember(propertyName, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public, null, source, null);
            target.GetType().InvokeMember(propertyName, BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.Public, null, target, new object[] { val });
        }
        protected virtual void Model_ShowView(object sender, EventArgs e)
        {
            View.Show();
        }
        protected virtual void View_AcceptChanges(object sender, EventArgs e)
        {
            Model.PersistChanges();
            Model.Navigate();
        }
        protected virtual void Model_NotifyUser(object sender, NotifyUserEventArgs e)
        {
            View.NotifyUser(e.Message);
        }
        #endregion

        #region Properties
        private IModel _model;
        protected virtual IModel Model
        {
            get { return _model; }
        }
        private IView _view;
        protected virtual IView View
        {
            get { return _view; }
        }
        #endregion 

        #region Methods
        public void Show()
        {
            Model.Show();
        }
        #endregion
    }

    #region IModel
    public interface IModel: INotifyPropertyChanged 
    {
        void Show();
        event EventHandler ShowView;  //Implemented by all Model classes. Called from Presenter.Show()
        void PersistChanges();
        void Navigate();
        event EventHandler<NotifyUserEventArgs> NotifyUser;
    }
    #endregion

    #region IView
    public interface IView: INotifyPropertyChanged 
    {
        void Show(); //Implemented by all views. Is usually triggered by the model Show event.
        event EventHandler AcceptChanges;
        void NotifyUser(string message);
    }
    #endregion

    #region EventArgs Classes
    public class NotifyUserEventArgs : EventArgs
    {
        public NotifyUserEventArgs(string message)
        { _message = message; }
        private string _message;
        public string Message
        { get { return _message; } }
    }
    #endregion

    #region Abstract Model
    public abstract class ModelBase : IModel
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnNotifyPropertyChanged(PropertyChangedEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            PropertyChangedEventHandler handler = PropertyChanged;
            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }

        public event EventHandler ShowView;
        protected virtual void OnShow(EventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler handler = ShowView;
            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }

        //Actions
        public abstract void Show();
        public abstract void PersistChanges();
        public abstract void Navigate();
        
        //Events
        public event EventHandler<NotifyUserEventArgs> NotifyUser;
        protected virtual void OnNotifyUser(NotifyUserEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<NotifyUserEventArgs> handler = NotifyUser;
            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }

        //Properties
        private bool _acceptEnabled;
        public virtual bool AcceptEnabled
        {
            get { return _acceptEnabled; }
            set
            {
                if (value != _acceptEnabled)
                {
                    _acceptEnabled = value;
                    OnNotifyPropertyChanged(new PropertyChangedEventArgs("AcceptEnabled"));
                }
            }
        }
        private CustomerDataContract _customer;
        public virtual CustomerDataContract Customer
        {
            get { return _customer; }
            set
            {
                if (!object.ReferenceEquals(_customer, value))
                {
                    _customer = value;
                    OnNotifyPropertyChanged(new PropertyChangedEventArgs("Customer"));
                }
            }
        }

    }
    #endregion

    #region Partial View
    public partial class Form2View : IView
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnNotifyPropertyChanged( PropertyChangedEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            PropertyChangedEventHandler handler = PropertyChanged;
            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }

        public event EventHandler AcceptChanges;
        protected virtual void OnAcceptChanges(EventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler handler = AcceptChanges;
            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }

        //Properties
        private bool _acceptEnabled;
        public virtual bool AcceptEnabled
        {
            get { return _acceptEnabled; }
            set
            {
                if (value != _acceptEnabled)
                {
                    _acceptEnabled = value;
                    OnNotifyPropertyChanged(new PropertyChangedEventArgs("AcceptEnabled"));
                }
            }
        }

        private CustomerDataContract _customer;
        public virtual CustomerDataContract Customer
        {
            get { return _customer;  }
            set 
            { 
                if(!object.ReferenceEquals(_customer,value))
                {
                    _customer = value;
                    OnNotifyPropertyChanged(new PropertyChangedEventArgs("Customer"));
                }
            }
        }
    }
    #endregion
}